Builder.cz - http://www.builder.cz
Autor: Petr Krebs
Rubrika: Delphi
Datum vydßnφ: 21.08. 2002
Url: http://www.builder.cz/art/delphi/optimdelphi.html

Optimßlnφ k≤d v Delphi

Co je to vlastn∞ optimßlnφ k≤d

Jako optimßlnφ k≤d bych oznaΦil takov² zßpis programu, kter² nemß ₧ßdnΘ zßva₧n∞jÜφ nedostatky ve svΘm v²konu, p°esto₧e obecn∞ panuje domn∞nka, ₧e optimßlnφ rovnß se nejrychlejÜφ. Je to v∞c Φist∞ subjektivnφ a zßle₧φ p°edevÜφm na tom, co od programu oΦekßvßme. Malß utilitka nebo st°edn∞ velkß aplikace nejspφÜ v∞tÜφ optimalizaci k≤du pot°ebovat nebude, opakem m∙₧e b²t nap°φklad 3D aplikace.
Pokud dojde na optimalizaci, je dobrΘ vytyΦit si jasn² cφl a v∞d∞t kdy s nφ skonΦit, nap°φklad pokud si programßtor urΦφ 28 fps ve svΘ h°e.

Rychlost vs. ╚itelnost

Mnoho lidφ se domnφvß, ₧e s rychl²m k≤dem jde ruku v ruce oÜkliv² a neΦiteln² k≤d. V n∞kter²ch p°φpadech tomu tak opravdu b²vß a vyhnout se tomu dß jen t∞₧ko, p°esto vÜak obecn∞ platφ, ₧e takov² k≤d m∙₧e b²t nejen dob°e Φiteln², ale dokonce i velmi elegantnφ.

┌rove≥ optimalizace

P°i psanφ slo₧it∞jÜφho programu jste urΦit∞ n∞kdy narazili na to, ₧e napsan² algoritmus je pro vaÜe pot°eby p°φliÜ pomal². Je t°eba rozhodnout, zda je dan² algoritmus ten vhodn², a p°φpadn∞ se poohlΘdnout po jinΘm. Optimalizace mß obecn∞ dv∞ ·rovn∞ - optimalizace na ·rovni algoritmu a na ·rovni implementace algoritmu. Jestli₧e si jste jisti, ₧e algoritmus je kvalitnφ, nebo p°inejmenÜφm nejlepÜφ mo₧n², p°ichßzφ na °adu poupravit jeho k≤d, tedy to, jak²mi prost°edky Object Pascalu je zapsßn.
V ₧ßdnΘm p°φpad∞ to neznamenß, ₧e by jste se m∞li pokouÜet optimalizovat ka₧diΦk² °ßdek svΘho programu. PostaΦφ vylepÜit ty Φßsti, pro kterΘ je rychlost d∙le₧itß, nebo identifikovat ty Φßsti, kterΘ jsou opravdu p°φliÜ pomalΘ.
Velk² klam je to, ₧e optimalizace p°ichßzφ a₧ tehdy, kdy₧ je k≤d hotov². Mnohem lepÜφ a z programßtorskΘho hlediska takΘ profesionßln∞jÜφ je nauΦit se psßt kvalitnφ a rychl² k≤d u₧ od zaΦßtku. K tomu je ovÜem pot°eba se seznßmit se vÜemi pravidly psanφ rychlΘho program∙, a prßv∞ to mß b²t hlavnφm tΘmatem tohoto Φlßnku.

Hlavnφ zßsady

V jednoduchosti je sφla

P°ekladaΦ (kompilßtor, compiler) Delphi automaticky p°i p°eklßdßnφ tvΘho k≤du provßdφ n∞kterΘ zßsadnφ optimalizace, obsahuje toti₧ vlastnφ optimizßtor (optimizΘr, optimizer). Ten se starß nejen o jednoduchΘ v∞ci jako je t°eba odstra≥ovßnφ zbyteΦn²ch °ßdk∙ k≤du, jeho hlavnφ pracφ je pochopit nßÜ "slo₧it²" k≤d a upravit jej tak, aby se dal co nejefektivn∞ji p°elo₧it do spustitelnΘho souboru.
Platφ pravidlo, ₧e Φφm je k≤d jednoduÜÜφ, tφm ·sp∞Ün∞jÜφ optimizer je. DoporuΦuje se v jednΘ rutin∞ nepou₧φvat vφce ne₧ 5 prom∞nn²ch (variables). Nenφ takΘ radno provßd∞t b∞hem jednΘ smyΦky p°φliÜ mnoho operacφ, to toti₧ Φasto vede k realokaci °φdφcφch nebo pomocn²ch prom∞nn²ch. ╚asto je v²hodnΘ dlouhΘ smyΦky rozd∞lit do vφce jednotliv²ch nebo v nich provßd∞nΘ operace p°emφstit do samostatnΘ funkce nebo procedury (obecn∞ rutiny). P°φjemn²m vedlejÜφm efektem b²vß takΘ zv²Üenφ Φitelnosti takovΘho k≤du.

Pou₧φvejte lokßlnφ prom∞nnΘ

Jen lokßlnφ prom∞nnΘ, tedy ty, kterΘ jsou definovanΘ p°φmo v rutin∞, mohou b²t p°i jejφm spuÜt∞nφ p°evedeny do procesorov²ch registr∙, co₧ je velmi v²znamnΘ pro rychlost. N∞kdy je v²hodnΘ prom∞nnΘ do lokßlnφch prom∞nn²ch vynucen∞ zkopφrovat, zejmΘna pokud jsou pou₧ity v n∞jakΘ smyΦce. To se t²kß hlavn∞ vlastnostφ t°φd (tedy t°eba komponent).
V²jimka z tohoto pravidla jsou pole (arrays) s elementy n∞kterΘho jednoduchΘho typu (t°eba integer, byte, char, atd.). Ty je n∞kdy v²hodnΘ za°adit mezi globßlnφ prom∞nnΘ, ale samoz°ejm∞ jen tehdy, pokud to mß v rßmci celΘho programu n∞jak² v∞tÜφ smysl.

MalΘ mno₧stvφ parametr∙

Hodn∞ Φasto pou₧φvanΘ rutiny by nem∞ly mφt vφce ne₧ t°i parametry, neb t°i je p°esn∞ poΦet prom∞nn²ch, kterΘ mohou b²t v jeden Φas umφst∞ny do registru. Tφmto se rychlosti procesorov²ch registr∙ vyu₧ije na maximum a optimizer dostane v∞tÜφ Üanci vylepÜit vßÜ k≤d. Metody t°φd vÜak majφ skryt² parametr self, kter²m odkazujφ na konkrΘtnφ instanci danΘ t°φdy, zb²vajφ tedy tak u₧ jen dva parametry k umφst∞nφ do registr∙.

Ukazatele

Cennß technika je vyu₧φvat v²hod, kterΘ p°inßÜejφ ukazatele (pointers). Jejich pou₧φvßnφ p°inßÜφ optimizeru mnoho informacφ o tvΘm k≤du, ale to samoz°ejm∞ neznamenß abyste vÜechny objekty referencovali jako ukazatele. Pokud definujete objekt t°eba takto

var Soubor   : TObject;
      Ukazatel : Pointer; 

mo₧nß ani nevφte, ₧e prom∞nnß Soubor je vlastn∞ ukazatel, ukazuje na urΦitΘ mφsto v pam∞ti a navφc urΦuje, jakß data jsou v danΘm pam∞¥ovΘm ·seku zapsßny. Toho lzes v²hodou vyu₧φvat, a do prom∞nn²ch Pointer uklßdat reference na objekty jakΘhokoli typu.

begin
   Soubor := TObject.Create;
   Ukazatel := Soubor;
   TObject(Ukazatel).Free;
  end; 
  

Pole

Pokud to jde, nepou₧φvejte dynamickß pole. Prßce s nimi je oproti klasick²m (tedy statick²m) polφm o dost pomalejÜφ. Musφte-li je pou₧φt, nezapomφnejte na funkce High or Length pro zjiÜ¥ovßnφ velikosti pole. Ve skuteΦnosti fce High volß fci Length, Length je proto doporuΦovan∞jÜφ. NezjiÜ¥ujte ale opakovan∞ velikost pole t∞mito funkcemi, je rychlejÜφ jej zjistit jednou a ulo₧it do lokßlnφ prom∞nnΘ. Jednou z mnoha vlastnostφ Delphi ulehΦujφcφch nßm prßci jsou tzv. open arrays. P°φkladem jejich pou₧itφ budi₧ tento k≤d:

var MalaTabulka      : Array[1..10]  of byte;
      VelkaTabulka     : Array[1..100] of byte;
      LibovolnaTabulka : Array of byte;

  procedure VypisTabulku(Tabulka : array of byte);
  var I : LongInt;
  begin
   for I := 0 to Length(X) - 1 do VypisHodnotu(Tabulka[i]);
  end;

Jak vidφte, parametr Tabulka neudßvß velikost p°edßvanΘho pole. ProblΘmem tΘto pohodlnΘ v∞ci je, ₧e volßnφ procedury VypisTabulku vytvo°φ lokßlnφ kopii celΘho pole, co₧, krom∞ faktu ₧e se tφm pl²tvß pam∞tφ, nenφ zrovna nejrychlejÜφ operace. V tomto p°φpad∞ je vhodnΘ parametr Tabulka p°edznamenat klφΦov²m slovem const nebo var, aby se tφm zabrßnilo vytvß°enφ lokßlnφ kopie. JeÜt∞ je t°eba poznamenat, ₧e pou₧itφ const zabrßnφ programßtorovi ve zm∞n∞ celΘho pole, ne u₧ tak jeho jednotliv²ch element∙, tak₧e pozor.

KonkrΘtnφ metody optimalizace


Mno₧iny

Pokud pro p°idßvßnφ/odebφrßnφ Φlen∙ do/z mno₧iny (set) pou₧φvßte klasickΘ operßtory (+ a -), v∞zte, ₧e o n∞co rychlejÜφ je prßce s rutinami Include a Exclude.

SmyΦka for

Jedna z nejΦast∞jÜφch konstrukcφ v Object Pascalu a zßrove≥ nejslo₧it∞jÜφ prßciΦka pro optimizer. Nap°φklad smyΦka

for I := M to N do
      A[i] := A[i] + B[i]

Bude optimizerem p°evedena do

  PA := @A[m];
  PB := @B[m];
  Pocitadlo := M - N + 1;
  if Pocitadlo > 0 then
     repeat
       PA^ := PA^ + PB^;
       Inc(PA);
       Inc(PB);
       Dec(Pocitadlo);
     Until Pocitadlo = 0;

Existujφ jinΘ mo₧nosti, ale tato je nejΦast∞jÜφ, a je z nφ dob°e vid∞t, kolik dodateΦnΘ prßce zdßnliv∞ tak jednoduchß v∞c, jako je for smyΦka, vy₧aduje. Platφ tedy, ₧e Φφm jednoduÜÜφ svou smyΦku ud∞lßte, tφm rychlejÜφ bude, a proto m∙₧e b²t n∞kdy lepÜφ slo₧itΘ smyΦky rozd∞lit do n∞kolika menÜφch.

Interfacy

Nevy₧adujφ p°φliÜ mnoho optimalizaΦnφ ·dr₧by. Obsahujφ v sob∞ tzv. reference counter (doslova p°elo₧eno jako ΦφtaΦ referencφ, odkaz∙), kter² zajiÜ¥uje uvoln∞nφ pam∞ti okupovanΘ jednou instancφ. Poka₧dΘ, kdy₧ je dan² interface nap°φklad p°i°azen n∞kterΘ prom∞nnΘ, zv²Üφ se RC o jeden, a analogicky se snφ₧φ v moment∞ kdy je n∞kter² odkaz na tento objekt zruÜen. Dosßhne-li RC nuly, je i objekt zruÜen. Proto si dßvej pozor a vyh²bej se znßmΘ technice, kterß vy₧aduje opakovanΘ ruÜenφ a znovuvytvß°enφ interface.

Pam∞¥ovΘ zarovnßvßnφ

Pou₧φvejte 32-bitovΘ prom∞nnΘ kdekoli a kdykoli je to mo₧nΘ. SouΦasnΘ procesory jsou u₧ n∞jak² ten pßtek 32-bitovΘ a prßce s 4 bytov²mi bloky dat je proto nejrychlejÜφ. Pro celoΦφselnΘ prom∞nnΘ to tedy nejΦast∞ji budou DWord, Longint nebo t°eba Cardinal. Kdy₧ musφte pou₧φvat prom∞nnΘ o jinΘ velikosti (nap°φklad z d∙vod∙ kompatibility), zm∞≥te je prost²m p°i°azenφm nejd°φve v 32-bitovΘ, a a₧ kdy₧ je pot°eba, zp∞t na p∙vodnφ velikost.

Zrychlovßnφ smyΦek

je jedna z nejΦast∞jÜφch optimizaΦnφch technik. V angliΦtin∞ se tomu °φkß loop unrolling. Z vlastnφch zkuÜenostφ ale m∙₧u °φct, ₧e tato technika je ·Φinnß jen u relativn∞ mal²ch smyΦek a to navφc jen p°i pou₧itφ konstrukce while. Viz. Tento k≤d:

  I := 0;
  while I < Count do
  begin
    Data[I] := Data[I] + 1;
    Inc(I);
  end;

Tato smyΦka m∙₧e b²t snadno zrychlena nßsledujφcφm zp∙sobem

  I := 0;
  while I < Count do
  begin
    Data[I]  := Data[I] + 1;
    Data[I + 1] := Data[I + 1] + 1;
    Inc(I, 2);
  end;

Tφmto se snφ₧φ poΦet pr∙chod∙ while smyΦkou. ProblΘm tΘto techniky tkvφ v pot°eb∞ dodateΦnΘho k≤du v p°φpad∞, ₧e Count je lichß hodnota a nenφ tedy d∞litelnß dv∞ma, a takΘ v tom, ₧e se tφm komplikuje Φitelnost k≤du. Poznßmka: Nemusφte se samoz°ejm∞ omezovat jen na dv∞ operace uvnit° smyΦky. Typicky se jich vÜak pro tento ·Φel nedoporuΦuje pou₧φvat vφce ne₧ 4, a to nejen kv∙li p°ehlednosti, ale zejmΘna kv∙li p°φliÜnΘ "slo₧itosti" k≤du pro optimizer. DalÜφ p°ipomφnkou je, ₧e vedle while se dß podobn∞ dob°e zrychlit i konstrukce repeat.

DalÜφ pravidla pro smyΦky

Uvnit° smyΦky pou₧φvejte co nejmΘn∞ podmφnek if, zvlßÜt∞ kdy₧ takovß podmφnka volß jinΘ rutiny. TakΘ se osv∞dΦuje omezit mno₧stvφ °φdφcφch podmφnek pro konstrukce while a repeat. Samoz°ejm∞ se ale ve v∞tÜin∞ p°φpad∙ slo₧it∞jÜφm podmφnkßm nevyhneme.

U₧φvejte v²hod Exit, Break a Continue

P°esto₧e tyto vlastnosti Object Pascalu b²vajφ pova₧ovßny za znßmku ÜpatnΘho a nekvalitnφho k≤du, majφ svΘ mφsto, a to hlavn∞ tam, kde je d∙le₧itß rychlost. Pro ostatnφ p°φpady se doporuΦuje pou₧φvat standardnφ konstrukce s podmφnkami a bloky beginàend.

for a while

Tam, kde je p°edem znßm² poΦet pr∙chod∙ smyΦky, se nejΦast∞ji pou₧φvajφ konstrukce for nebo while. Typicky bude zvolena spφÜe for, pro svou jednoduchost. V n∞kter²ch p°φpadech je ale while o n∞co efektivn∞jÜφ a optimizer takΘ n∞kdy, jak u₧ jsem ukßzal na p°φkladu v²Üe, konstrukce for p°evßdφ na konstrukce while. Platφ, ₧e tam, kde jsou v t∞le smyΦky pou₧ita pole s jednotliv²mi elementy o velikosti 1, 2, 4, nebo 8 byt∙, nebo nejsou pole pou₧ita v∙bec, je pou₧itφ while v²konn∞jÜφ. Ale tam, kde se objevujφ zejmΘna vφcerozm∞rnß pole nebo pole s elementy o jinΘ velikosti, b²vß rychlejÜφ naopak for.
Existuje vÜak jedna v²jimka - kdy₧ nenφ v t∞le smyΦky nikde pou₧ita °φdφcφ prom∞nnß a jde vßm tedy jen o urΦit² poΦet provedenφ n∞jakΘho k≤du, je for efektivn∞jÜφ mo₧nostφ.
Poznßmka: Zajφmavou vlastnostφ while v tomto p°φpad∞ je, ₧e dosahuje v∞tÜφho v²konu pokud °φdφcφ prom∞nnß nab²vß negativnφch hodnot a b∞hem jednotliv²ch pr∙b∞h∙ sm∞°uje sm∞rem k nule.

case

Implementace case je kompilßtorem pom∞rn∞ dob°e zvlßdnutß, i tato konstrukce se vÜak dß dßle optimalizovat. NejlΘpe si to ukß₧eme na p°φkladu:

  case x of
    100 :DoSomething1;
    101 :DoSomethingFrequently2;
    102 :DoSomething3;
    103 :DoSomething4;
    104 :DoSomething5;
    105 :DoSomething6;
    106 :DoSomething7;
    107 :DoSomething8;
    200 :DoSomething9;
    201 :DoSomething10;
    202 :DoSomething11;
    203 :DoSomething12;
    204 :DoSomething13;
    205 :DoSomething14;
    206 :DoSomething15;
    207 :DoSomething16;
  end;

Je zde vid∞t pom∞rn∞ velikß mezera mezi hodnotami 107 a 200, co₧ brßnφ optimizeru v efektivn∞jÜφ optimalizaci k≤du. Upravφme jej jako:

  case x of
    100..107 : 
	  case x of
        100 :DoSomething1;
        101 :DoSomethingFrequently2;
        102 :DoSomething3;
        103 :DoSomething4;
        104 :DoSomething5;
        105 :DoSomething6;
        106 :DoSomething7;
        107 :DoSomething8;
	  end;
	200..210 :
	  case x of
        200 :DoSomething9;
        201 :DoSomething10;
        202 :DoSomething11;
        203 :DoSomething12;
        204 :DoSomething13;
        205 :DoSomething14;
        206 :DoSomething15;
        207 :DoSomething16;
         end;
  end;

Kdy₧ vφte, ₧e n∞kterΘ hodnoty v case se objevujφ Φast∞ji, je dobrΘ je umφstit jeÜt∞ p°ed samotnou konstrukci:

  if X = 101 then
    DoSomethingFrequently2 else
    case X of
      100 :DoSomething1;
      102 :DoSomething3;
      103 :DoSomething4;
      104 :DoSomething5;
      105 :DoSomething6;
      106 :DoSomething7;
      107 :DoSomething8;
    end;

V²ΦtovΘ typy

Pokud to jde, vyhn∞te se v²Φtov²m typ∙m. Nap°φklad typ TRozsah = 0-6000 bude ulo₧en intern∞ jako Word, co₧, jak jsme si u₧ °ekli, je neefektivnφ. Podobn∞ bude mno₧ina o dejme tomu 250 elementech ulo₧ena jako byte.

CeloΦφselnΘ nßsobenφ

Nßsobenφ celoΦφseln²ch typ∙ je doslova utrpenφm pro starÜφ procesory jako je 80486, Pentium Φi Pentium Pro. Situace se zlepÜila s p°φchodem Pentia II. Kompilßtor si toho je v∞dom a vÜude, kde to jen trichu jde, nahrazuje takovΘ nßsobenφ jin²mi operacemi. Pokud tedy pφÜete k≤d, pro kter² je rychlost kritickß, musφte uva₧ovat i cφlov² systΘm a optimalizaci tomu p°izp∙sobit.

Zkracovßnφ podmφnek

Porovnßvßte-li prom∞nnou s vφce jednoduch²mi typy najednou, je p°ehledn∞jÜφ a efektivn∞jÜφ pou₧φt mφsto nich v²Φet. NejlepÜφ bude ukßzat si to na p°φkladu:

  if (((C > = 'a') and (C < = 'z')) or ((C > = '0') and (C < = '9'))) then
     DoSomething; 
  
  // zm∞≥te na
  if C in ['0'..'9', 'a'..'z'] then
     DoSomething;

  // p°φpadn∞ na
  case C of
     '0'..'9', 'a'..'z' : DoSomething;
     End;

DlouhΘ °et∞zce (AnsiString)

Nepou₧φvejte asociace pro poΦßteΦnφ vynulovßnφ °et∞zce na zaΦßtku rutiny. AnsiString (a tedy tφm pßdem string p°i standardnφm nastavenφ Delphi) je automaticky p°i svΘ inicializaci nastaven na prßzdn² °et∞zec. Pou₧φvßnφ operacφ typu S := S + S2[I] ve smyΦkßch zp∙sobuje dodateΦnou a pomalou alokaci pam∞ti p°i ka₧dΘm takovΘm zv∞tÜenφ stringu. Abyste tomu p°edeÜli, zavolejte nejd°φve SetLength(S, Nova_Delka). Toto je bohu₧el velmi Φastß chyba programßtor∙, zp∙sobenß jen tφm, ₧e zv∞tÜovßnφ a zmenÜovßnφ dynamick²ch °et∞zc∙ je v Delphi obstarßvßno automaticky.

Procedura Copy

╚astokrßt je k vid∞nφ, ₧e programßto°i pro odstran∞nφ urΦitΘ Φßsti °et∞zce pou₧φvajφ funkci Copy. Nap°φklad odstran∞nφ prvnφch 20ti znak∙ m∙₧e vypadat takto:

S := Copy(S, 21, Length(S) - 20);     // nesprßvn∞!!!
Delete(S, 1, 20);                     // mnohem, mnohem rychlejÜφ

string jako PChar

Pot°ebujete-li z n∞jak²ch d∙vod∙ na prom∞nou dynamickΘho °et∞zce (definovanou t°eba jako S) odkazovat jako na PChar, mßte t°i ekvivalentnφ mo₧nosti, p°iΦem₧ nejrychlejÜφ je ta poslednφ. Prvnφ je pou₧φt p°φmo PChar(S). Druhß tkvφ v referenci na prvnφ znak °et∞zce, tedy @S[1]. Tou poslednφ je ukßzat na °et∞zec p°φmo ukazatelem, pou₧φt tedy klasick² Pointer: Pointer(S).

DatovΘ typy s plovoucφ Φßrkou

Tyto floating types poskytujφ velikou flexibilitu, ale takΘ mohou zabrat mnoho pam∞ti. Stßle platφ, 32-bitovΘ typy jsou nejrychlejÜφ. Pov∞tÜinou ale pou₧φvßme tyto typy takΘ pro jejich Φasto velik² rozsah, proto n∞kdy je nutnΘ on∞ch 32-bit∙ nedodr₧et.
Prvnφ pravidlo znφ, pou₧φvejte Extended jen kdy₧ je to nezbytn∞ nutnΘ. Jejich velikost (10 byt∙) je zejmΘna pro zßkladnφ operace (p°edevÜφm +, -, *) vysoce neefektivnφ, a projevφ se to ve ztrßt∞ v²konu.
Dßle, sna₧te se tyto typy nemixovat - pokud u₧ musφte pou₧φvat n∞kterΘ z t∞chto "desetinn²ch" typ∙, dr₧ se jen jednoho z nich.
V p°φpad∞, ₧e definujete konstantu n∞kterΘho typu s plovoucφ Φßrkou, ud∞lejte z nφ konstantu s p°i°azen²m datov²m typem, a to Single nebo Double (const X : Single;). Pokud to neud∞lßte, bude ulo₧ena jako Extended, co₧ je siln∞ nepraktickΘ.
Namφsto Trunc pou₧φvejte Round. Ta je na Pentiu II p°ibli₧n∞ 2,74x rychlejÜφ.
Vyh²bejte se d∞lenφ kdekoli to jen jde, zejmΘna uvnit° smyΦek. Je asi 35x pomalejÜφ ne₧ ostatnφ b∞₧nΘ operace (!).

Poslednφ rada ohledn∞ t∞chto typ∙ °φkß, nepou₧φvejte je jako v²stup funkce. Mφsto toho definujte proceduru a dan² v²stup vracejte pomocφ var:

  function Fnc(X : Double) : Double;
    begin
      Result := X * 1.8 + 2;
    end;

  // po ·prav∞
  procedure Fnc(X : Double; var Result : Double)
    begin
      Result := X * 1.8 + 2;
    end;


╚ßsti Φlßnku p°evzaty z http://www.optimalcode.com/ s laskav²m svolenφm R.Lee


------ http://www.builder.cz ------